/*
	 Copyright (c) 2011, Micael Hildenborg
	 All rights reserved.

	 Contributors:
	 Gustav
	 Several members in the gamedev.se forum.
	 Gregory Petrosyan
 */

#include "sha1.h"

inline const unsigned int sha1_rol(const unsigned int value, const unsigned int steps)
{	//	Rotates an integer value to left
	return ((value << steps) | (value >> (32 - steps)));
}

inline void sha1_clearWBuffert(unsigned int* buffert)
{	//	Sets the first 16 integers in the buffert to zero
    for (int pos = 16; --pos >= 0;)
        buffert[pos] = 0;
}

void sha1_innerHash(unsigned int* result, unsigned int* w)
{
    unsigned int a = result[0];
    unsigned int b = result[1];
    unsigned int c = result[2];
    unsigned int d = result[3];
    unsigned int e = result[4];

    int round = 0;

    #define sha1macro(func,val) \
	{ \
        const unsigned int t = sha1_rol(a, 5) + (func) + e + val + w[round]; \
		e = d; \
		d = c; \
		c = sha1_rol(b, 30); \
		b = a; \
		a = t; \
	}

    while (round < 16)
    {
        sha1macro((b & c) | (~b & d), 0x5a827999)
        ++round;
    }
    while (round < 20)
    {
        w[round] = sha1_rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
        sha1macro((b & c) | (~b & d), 0x5a827999)
        ++round;
    }
    while (round < 40)
    {
        w[round] = sha1_rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
        sha1macro(b ^ c ^ d, 0x6ed9eba1)
        ++round;
    }
    while (round < 60)
    {
        w[round] = sha1_rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
        sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc)
        ++round;
    }
    while (round < 80)
    {
        w[round] = sha1_rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
        sha1macro(b ^ c ^ d, 0xca62c1d6)
        ++round;
    }

    #undef sha1macro	
    result[0] += a;
    result[1] += b;
    result[2] += c;
    result[3] += d;
    result[4] += e;
}

void sha1_calc_hash(const void* src, const int bytelength, unsigned char* hash)
{
	unsigned int result[5] = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 };
	const unsigned char* sarray = (const unsigned char*) src;	// Cast the void src pointer to be the working byte array
	unsigned int w[80];											// The reusable round buffer
	const int endOfFullBlocks = bytelength - 64;
	int endCurrentBlock;
	int currentBlock = 0;

	while (currentBlock <= endOfFullBlocks)
	{	//	Loop through all complete 64byte blocks.
		endCurrentBlock = currentBlock + 64;
		for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4)
		{	//	Swap endian on big endian and keep endian on little endian.
			w[roundPos++] = (unsigned int) sarray[currentBlock + 3]
					| (((unsigned int) sarray[currentBlock + 2]) << 8)
					| (((unsigned int) sarray[currentBlock + 1]) << 16)
					| (((unsigned int) sarray[currentBlock]) << 24);
		}
		sha1_innerHash(result, w);
	}

	// Handle the last and not full 64 byte block if existing.
	endCurrentBlock = bytelength - currentBlock;
	sha1_clearWBuffert(w);
	int lastBlockBytes = 0;

	for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes)
		w[lastBlockBytes >> 2] |= (unsigned int) sarray[lastBlockBytes + currentBlock] << ((3 - (lastBlockBytes & 3)) << 3);

	w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3);
	if (endCurrentBlock >= 56)
	{
		sha1_innerHash(result, w);
		sha1_clearWBuffert(w);
	}
	w[15] = bytelength << 3;
	sha1_innerHash(result, w);

	// Store hash in result pointer, and make sure we get in in the correct order on both endian models.
	for (int hashByte = 20; --hashByte >= 0;)
		hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff;
}

void sha1_to_hexstring(const unsigned char* hash, char* hexstring)
{
	const char hexDigits[] = { "0123456789abcdef" };
	for (int hashByte = 20; --hashByte >= 0;)
	{
		hexstring[hashByte << 1] = hexDigits[(hash[hashByte] >> 4) & 0xf];
		hexstring[(hashByte << 1) + 1] = hexDigits[hash[hashByte] & 0xf];
	}
	hexstring[40] = 0;
}

string	sha1(string plaintext)
{
	unsigned char hash[20];
	char hexstring[41];
	sha1_calc_hash(plaintext.c_str(), plaintext.length(), hash);
	sha1_to_hexstring(hash, hexstring);
	return hexstring;
}

